package com.sifou.courses.service.impl;

import com.sifou.courses.Constant;
import com.sifou.courses.dto.SeckillGoodsDto;
import com.sifou.courses.dto.SeckillOrderDto;
import com.sifou.courses.exception.CustomException;
import com.sifou.courses.repository.entity.TSeckillGoods;
import com.sifou.courses.repository.mapper.TSeckillGoodsMapper;
import com.sifou.courses.service.ISeckillService;
import lombok.extern.slf4j.Slf4j;
import org.redisson.api.RedissonClient;
import org.springframework.beans.BeanUtils;
import org.springframework.retry.RetryException;
import org.springframework.stereotype.Service;

import javax.annotation.Resource;
import java.util.HashMap;
import java.util.Map;
import java.util.Objects;

/**
 * 使用乐观锁加缓存的方式(线程安全)(使用Redis批量操作mget和mset具有原子性)
 * @author liuzhongxu
 * @date 2020/8/18
 */
@SuppressWarnings("AlibabaRemoveCommentedCode")
@Slf4j
@Service("seckillOptimisticLockRedisSafeService")
public class SeckillOptimisticLockRedisSafeServiceImpl implements ISeckillService {

    @Resource
    private RedissonClient redissonClient;

    @Resource
    private TSeckillGoodsMapper goodsMapper;

    @Override
    public SeckillGoodsDto checkStock(Integer id) {
        String[] keys = {Constant.PREFIX_COUNT + id, Constant.PREFIX_SALE + id, Constant.PREFIX_VERSION + id};
        Map<String, Integer> buckets = redissonClient.getBuckets().get(keys);
        // 库存
        Integer count = buckets.get(Constant.PREFIX_COUNT + id);
        // 已售
        Integer sale = buckets.get(Constant.PREFIX_SALE + id);
        // 版本号
        Integer version = buckets.get(Constant.PREFIX_VERSION + id);
        if (Objects.nonNull(count) && count > 0) {
            log.debug("库存充足：count=" + count);
            SeckillGoodsDto goodsDto = new SeckillGoodsDto();
            goodsDto.setId(id);
            goodsDto.setCount(count);
            goodsDto.setSale(sale);
            goodsDto.setVersion(version);
            return goodsDto;
        }
        log.debug("库存不足：count=" + count);
        throw new CustomException("库存不足");
    }

    @Override
    public Integer saleStock(SeckillGoodsDto goodsDto) {
        TSeckillGoods tSeckillGoods = new TSeckillGoods();
        BeanUtils.copyProperties(goodsDto, tSeckillGoods);
        log.debug("库存：count={} 已售：sale={}", goodsDto.getCount(), goodsDto.getSale());
        Integer opCount = goodsMapper.updateByOptimisticLock(tSeckillGoods);
        if (opCount > 0) {
            // 更新缓存
            updateCache(goodsDto, 1);
        }
        return opCount;
    }

    @Override
    public Integer rollBackStock(SeckillOrderDto orderDto) {
        TSeckillGoods tSeckillGoods = goodsMapper.selectById(orderDto.getGoodsId());
        Integer opCount =  goodsMapper.rollBackByOptimisticLock(tSeckillGoods);
        if (opCount > 0) {
            log.debug("回滚库存成功：count={} 已售：sale={} 订单：{}", tSeckillGoods.getCount(), tSeckillGoods.getSale(), orderDto);
            SeckillGoodsDto goodsDto = new SeckillGoodsDto();
            BeanUtils.copyProperties(tSeckillGoods, goodsDto);
            updateCache(goodsDto, 2);
            return opCount;
        }
        log.debug("回滚库存失败：count={} 已售：sale={} 订单：{}", tSeckillGoods.getCount(), tSeckillGoods.getSale(), orderDto);
        throw new RetryException("回滚库存失败");
    }

    private void updateCache(SeckillGoodsDto goodsDto,Integer type) {
        Integer id = goodsDto.getId();
        Integer sale;
        Integer count;
        if (type == 1) {
            // 扣库存
            count = goodsDto.getCount() - 1;
            sale = goodsDto.getSale() + 1;
        } else {
            // 回滚库存
            count = goodsDto.getCount() + 1;
            sale = goodsDto.getSale() - 1;
        }

        Integer version = goodsDto.getVersion() + 1;
        Map<String, Integer> buckets = new HashMap<>();
        buckets.put(Constant.PREFIX_COUNT + id, count);
        buckets.put(Constant.PREFIX_SALE + id, sale);
        buckets.put(Constant.PREFIX_VERSION + id, version);
        redissonClient.getBuckets().set(buckets);
    }
}
